######################################################################
# File: server/net/telnet.py                       Part of DjangoMUD #
#                                                                    #
# Copyright (C) 2010, Joachim Pileborg.                              #
# All rights reserved.                                               #
#                                                                    #
# Redistribution and use in source and binary forms, with or without #
# modification, are permitted provided that the following conditions #
# are met:                                                           #
#                                                                    #
#   o Redistributions of source code must retain the above copyright #
#     notice, this list of conditions and the following disclaimer.  #
#   o Redistributions in binary form must reproduce the above        #
#     copyright notice, this list of conditions and the following    #
#     disclaimer in the documentation and/or other materials         #
#     provided with the distribution.                                #
#   o Neither the name of the author nor the names of DjangoMUDs     #
#     contributors may be used to endorse or promote products        #
#     derived from this software without specific prior written      #
#     permission.                                                    #
#                                                                    #
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND             #
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,        #
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF           #
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE           #
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS  #
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,#
# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,           #
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR #
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY       #
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR     #
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF #
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF    #
# SUCH DAMAGE.                                                       #
######################################################################

from django.conf import settings
from django.contrib.auth.models import User
from django.contrib import auth

from twisted.conch.telnet import TelnetTransport, StatefulTelnetProtocol
from twisted.conch import telnet
from twisted.internet.error import ConnectionDone
from twisted.internet import reactor

from djangomud.server.net.factory import Factory
from djangomud.server.connection import *
from djangomud import log
from djangomud.utils import templates

######################################################################

class TelnetProtocol(StatefulTelnetProtocol, Connection):
    """
    Protocol handler for the telnet protocol.
    """

    class CreateUserInfo(object):
        username = ''
        sex = ''
        real_name = ''
        email = ''
        password = ''

    create_user_info = None

    ##################################################################

    def connectionMade(self):
        StatefulTelnetProtocol.connectionMade(self)
        self.transport.factory.addConnection(self)
        self.write(templates.render_to_string(settings.TPL_WELCOME_SCREEN))
        self.write('Your name please: ')
        self.state = 'login_get_name'
        log.net('New connection from ' + self.transport.getPeer().host)

    def connectionLost(self, reason):
        log.net('Connection to %s lost: %s', self.transport.getPeer().host, reason.getErrorMessage())
        self.transport.factory.removeConnection(self)
        StatefulTelnetProtocol.connectionLost(self, reason)

    ##################################################################
    # Connection overrides

    def write(self, message, wrap=False):
        if self.state == 'playing' and message[-1] == '\n':
            message += self.user.get_profile().prompt or '> '
        # TODO: Check if user wants UTF-8 or plain ASCII
        if wrap:
            import textwrap
            self.transport.write(textwrap.fill(message).encode('ascii'))
        else:
            self.transport.write(message.encode('ascii'))

    def echo_off(self):
        """Turn echoing of input off."""
        self.transport.will(telnet.ECHO)

    def echo_on(self):
        """Turn echoing of input back on."""
        self.transport.wont(telnet.ECHO)

    ##################################################################
    # State functions

    def telnet_login_get_name(self, line):
        try:
            self.user = User.objects.get(username__exact=line)
        except User.DoesNotExist:
            self.write('No player with that name "%s" exist, do you want to create one? (y/N) ' % line)
            self.state = 'login_get_name_confirmation'
            self.create_user_info = TelnetProtocol.CreateUserInfo()
            self.create_user_info.username = line
        else:
            self.write('Password: ')
            self.echo_off()
            self.state = 'login_get_password'

    def telnet_login_get_name_confirmation(self, line):
        if line.lower() == 'y' or line.lower() == 'yes':
            self.write('\r\nWelcome to %s!\r\n' % settings.MUD_NAME)
            self.write('''
You are now about to create a new user. During this process you will be
asked a set of questions, but only a few of them are mandatory.

Ok, lets start...

Please enter a password for your user: ''')
            self.state = 'create_player_get_password'
        else:
            self.write('Ok, what is your name then? ')
            self.state = 'login_get_name'

    def telnet_create_player_get_password(self, line):
        if not line or len(line) < 4:
            self.write('That is not a valid password, it has to be at least 4 characters.\r\n' +
                       'Your password please: ')
        else:
            self.create_user_info.password = line
            self.write('Please confirm your password: ')
            self.echo_off()
            self.state = 'create_player_confirm_password'

    def telnet_create_player_confirm_password(self, line):
        self.echo_on()
        if line != self.create_user_info.password:
            self.write('\r\nPasswords does not match!\r\nStart over with the password: ')
            self.state = 'create_player_get_password'
        else:
            self.write('''
Ok, time to choose sex of your character in game:
  1) Male
  2) Female

Please pick one: ''')
            self.state = 'create_player_choose_sex'

    def telnet_create_player_choose_sex(self, line):
        if line in ('1', '2'):
            if line == '1':
                self.create_user_info.sex = 'male'
            else:
                self.create_user_info.sex = 'female'
            self.write('''
Now you can enter your real name if you want to. Note that this is completely
optional. Just press enter without typing anything to leave it blank.

Your real name (in the form Firstname Lastname): ''')
            self.state = 'create_player_get_real_name'
        else:
            self.write('''That is not a valid choice.
  1) Male
  2) Female

Please pick one: ''')

    def telnet_create_player_get_real_name(self, line):
        if not line:
            self.write('''
Ok, you don't want to give us you real name, that is ok. You can now opt to
give us an email address we can reach you on. This is also completely optional,
just press enter without typing anything to leave it blank.

Your email address: ''')
            self.state = 'create_player_get_email'
        else:
            try:
                firstname, lastname = line.split(' ')
            except ValueError:
                firstname, lastname = '', ''
            if not firstname or not lastname:
                self.write('''That is not a valid name.
Your real name (in the form Firstname Lastname): ''')
            else:
                self.create_user_info.real_name = line
                self.write('''
You can now opt to give us an email address we can reach you on. This is also completely optional, just press enter without typing anything to leave it blank.

Your email address: ''')
                self.state = 'create_player_get_email'

    def telnet_create_player_get_email(self, line):
        if line:
            from django.core.validators import validate_email
            from django.core.exceptions import ValidationError
            try:
                validate_email(line)
                self.create_user_info.email = line
            except ValidationError:
                self.write('''
That is not a valid email address. Please try again. Remember that if you don't
want to give your email address, just press ENTER withouth typing anything.

Your emil address: ''')
                return

        self.write('''
Thank you for your patience! Your player character will now be created, and
you will very soon be insinde the game playing with everyone else.
''')
        self.user = User.objects.create_user(self.create_user_info.username,
                                             self.create_user_info.email,
                                             self.create_user_info.password)
        if self.user is None:
            self.write('Bad news! It seems that there was an error creating your player.')
            self.write('''
Please contact one of these people and tell them about it, and remember to
give the person you contact as much information as you can:
''')
            for admin in settings.ADMINS:
                self.write('    %s - %s' % (admin[0], admin[1]))
            log.error('Error creating user for new player:')
            log.error('    username  = %r' % self.create_user_info.username)
            log.error('    real_name = %r' % self.create_user_info.real_name)
            log.error('    email     = %r' % self.create_user_info.email)
            log.error('    sex       = %r' % self.create_user_info.sex)
            self.loseConnection()
        else:
            self.player = self.user.get_profile()
            if self.create_user_info.real_name:
                self.user.first_name, self.user.last_name = self.create_user_info.real_name.split(' ')
            self.user.username = self.create_user_info.username
            self.user.email = self.create_user_info.email
            self.user.set_password(self.create_user_info.password)
            # TODO: self.player.sex = ...
            self.player.connection = self
            self.player.last_login_from = self.transport.getPeer().host
            self.user.save()
            self.player.save()
            # TODO: Log this
            self.state = 'playing'
            self.write('\r\nAll set, please enjoy the world of %s!\r\n\r\n' % settings.MUD_NAME)

    def telnet_login_get_password(self, line):
        user = auth.authenticate(username=self.user.username, password=line)
        if user is None:
            self.user.get_profile().login_tries += 1
            self.user.get_profile().save()
            if self.user.get_profile().login_tries % 3 == 0:
                self.write('\r\nTo many attempts to guess password, disconnecting.\r\n')
                log.net('User %r disconnected: To many password attempts (%d total)' %
                        ((self.user.get_full_name() or self.user.username),
                         self.user.get_profile().login_tries))
                self.transport.loseConnection()
            else:
                self.write('\r\nWrong password, Please try again: ')
        else:
            self.echo_on()
            self.user = user
            self.player = user.get_profile()
            if self.user.get_profile().login_tries > 0:
                self.write('\r\n\x07\x07\x07%d failed password attempts.\r\n\r\n' %
                           self.user.get_profile().login_tries)
                self.user.get_profile().login_tries = 0
            log.debug('login: self = %s' % self)
            log.debug('login: self.player = %s' % self.player)
            log.debug('login: self.player.connection 1 = %s' % self.player.connection)
            self.player.connection = self
            log.debug('login: self.player.connection 2 = %s' % self.player.connection)
            self.player.last_login_from = self.transport.getPeer().host
            self.player.save()
            # TODO: Log this!
            self.state = 'playing'
            self.write('Welcome to %s, %s.\r\n' %
                       (settings.MUD_NAME, (self.user.get_full_name() or self.user.username.capitalize())))

    def telnet_playing(self, line):
        """
        This is the default playing state. When a connection is in this state,
        it means the user is logged in, authenticated and is playing inside the MUD.
        """
        if line == 'quit':
            # TODO: Log this
            self.transport.loseConnection()
        elif line == 'shutdown':
            if self.user.is_authenticated() and self.user.is_superuser:
                from djangomud.server import shutdown
                shutdown.shutdown()
                log.info('shutdown command issued by %s' % self.user.username)
                return
        else:
            from djangomud.commands import execute_command
            log.debug('playing: self = %s' % self)
            log.debug('playing: self.player = %s' % self.player)
            log.debug('playing: self.player.connection = %s' % self.player.connection)
            execute_command(line, self.player)

######################################################################

class TelnetProtocolFactory(Factory):
    port = settings.DEFAULT_TELNET_PORT  # Port to bind to
    addr = settings.DEFAULT_TELNET_ADDR  # Address to bind to
    name = 'telnet'
    protocol = lambda self: TelnetTransport(TelnetProtocol)

######################################################################

# def kalle(s1, s2 = None):
#     def _outer(func):
#         def _inner(*args, **kwargs):
#             print 's1=%r, s2=%r' % (s1, s2 or '<None>')
#             print 'function %r is in module %r' % (func.__name__, func.__module__)
#             func(*args, **kwargs)
#         return _inner
#     return _outer

# @kalle('hello', 'world')
# def hepp():
#     print 'Hopp'

# class Pelle(object):
#     def __init__(self):
#         super(Pelle, self).__init__()
#     @kalle('only')
#     def hopp(self):
#         print 'Hepp'

# class Olle(Pelle):
#     pass
